// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_segment/chart_segment.dart';
import 'package:charts_grasp/src/chart/chart_segment/line_segment.dart';
import 'package:charts_grasp/src/chart/chart_series/line_series.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/common/renderer.dart';
import 'package:charts_grasp/src/chart/common/segment_properties.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/common/event_args.dart';
import 'package:charts_grasp/src/common/rendering_details.dart';
import 'package:flutter/material.dart';

class LineSeriesRenderer extends XyDataSeriesRenderer {
  LineSeriesRenderer();

  late LineSegment _lineSegment;
  late LineSegment _segment;

  List<CartesianSeriesRenderer>? _oldSeriesRenderers;

  late SeriesRendererDetails _currentSeriesDetails;
  late SeriesRendererDetails _segmentSeriesDetails;

  ChartSegment _createSegments(CartesianChartPoint<dynamic> currentPoint, CartesianChartPoint<dynamic> nextPoint,
      int pointIndex, int seriesIndex, double animateFactor,) {
    _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this);
    _segment = createSegment();
    SegmentHelper.setSegmentProperties(_segment, SegmentProperties(_currentSeriesDetails.stateProperties, _segment));
    final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(_segment);
    SegmentHelper.setSegmentProperties(_segment, segmentProperties);
    _oldSeriesRenderers = _currentSeriesDetails.stateProperties.oldSeriesRenderers;
    segmentProperties.series = _currentSeriesDetails.series as XyDataSeries<dynamic, dynamic>;
    segmentProperties.seriesRenderer = this;
    segmentProperties.seriesIndex = seriesIndex;
    segmentProperties.currentPoint = currentPoint;
    _segment.currentSegmentIndex = pointIndex;
    segmentProperties.nextPoint = nextPoint;
    _segment.animationFactor = animateFactor;
    segmentProperties.pointColorMapper = currentPoint.pointColorMapper;
    _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer);
    if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == true &&
        _oldSeriesRenderers != null &&
        _oldSeriesRenderers!.isNotEmpty &&
        _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex &&
        SeriesHelper.getSeriesRendererDetails(_oldSeriesRenderers![segmentProperties.seriesIndex]).seriesName ==
            _segmentSeriesDetails.seriesName) {
      segmentProperties.oldSeriesRenderer = _oldSeriesRenderers![segmentProperties.seriesIndex];
      segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment);
    }
    _segment.calculateSegmentPoints();
    _segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1));
    _segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2));
    customizeSegment(_segment);
    _currentSeriesDetails.segments.add(_segment);
    return _segment;
  }


  @override
  LineSegment createSegment() => LineSegment();

  @override
  void customizeSegment(ChartSegment segment) {
    _lineSegment = segment as LineSegment;
    final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(_lineSegment);
    segmentProperties.color =
        segmentProperties.pointColorMapper ?? segmentProperties.series.color ?? _segmentSeriesDetails.seriesColor;
    segmentProperties.strokeColor =
        segmentProperties.pointColorMapper ?? segmentProperties.series.color ?? _segmentSeriesDetails.seriesColor;
    segmentProperties.strokeWidth = segmentProperties.series.width;
    _lineSegment.strokePaint = _lineSegment.getStrokePaint();
    _lineSegment.fillPaint = _lineSegment.getFillPaint();
  }

  @override
  void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY,
      [CartesianSeriesRenderer? seriesRenderer,]) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer!);
    canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint);
    canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint);
  }

  @override
  void drawDataLabel(
          int index, Canvas canvas, String dataLabel, double pointX, double pointY, int angle, TextStyle style,) =>
      drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle);
}

class LineChartPainter extends CustomPainter {
  LineChartPainter({
    required this.stateProperties,
    required this.seriesRenderer,
    required this.isRepaint,
    required this.animationController,
    required ValueNotifier<num> notifier,
    required this.painterKey,
  })  : chart = stateProperties.chart,
        super(repaint: notifier);

  final CartesianStateProperties stateProperties;

  final CgCartesianChart chart;

  final bool isRepaint;

  final AnimationController animationController;

  final LineSeriesRenderer seriesRenderer;

  final PainterKey painterKey;

  @override
  void paint(Canvas canvas, Size size) {
    double animationFactor;
    Rect clipRect;
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    disposeOldSegments(chart, seriesRendererDetails);

    final ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!;
    final ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!;
    final RenderingDetails renderingDetails = stateProperties.renderingDetails;
    final List<CartesianChartPoint<dynamic>> dataPoints = seriesRendererDetails.dataPoints;
    final LineSeries<dynamic, dynamic> series = seriesRendererDetails.series as LineSeries<dynamic, dynamic>;
    if (seriesRendererDetails.visible! == true) {
      assert(series.animationDuration >= 0,
          'The animation duration of the fast line series must be greater or equal to 0.',);
      canvas.save();
      final int seriesIndex = painterKey.index;
      seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex);
      animationFactor =
          seriesRendererDetails.seriesAnimation != null ? seriesRendererDetails.seriesAnimation!.value : 1;
      stateProperties.shader = null;
      if (series.onCreateShader != null) {
        stateProperties.shader =
            series.onCreateShader!(ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series'));
      }
      final Rect axisClipRect = calculatePlotOffset(
          stateProperties.chartAxis.axisClipRect, Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset),);
      canvas.clipRect(axisClipRect);
      if (seriesRendererDetails.reAnimate == true ||
          ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) ||
                  !stateProperties.oldSeriesKeys.contains(series.key)) &&
              series.animationDuration > 0)) {
        performLinearAnimation(stateProperties, xAxisDetails.axis, canvas, animationFactor);
      }
      int segmentIndex = -1;
      CartesianChartPoint<dynamic>? currentPoint;
      CartesianChartPoint<dynamic>? nextPoint;
      CartesianChartPoint<dynamic>? startPoint;
      CartesianChartPoint<dynamic>? endPoint;

      if (seriesRendererDetails.visibleDataPoints == null ||
          seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) {
        seriesRendererDetails.visibleDataPoints = <CartesianChartPoint<dynamic>>[];
      }

      seriesRendererDetails.setSeriesProperties(seriesRendererDetails);
      for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) {
        currentPoint = dataPoints[pointIndex];
        bool withInXRange = withInRange(currentPoint.xValue, seriesRendererDetails.xAxisDetails!);
        bool withInYRange =
            currentPoint.yValue != null && withInRange(currentPoint.yValue, seriesRendererDetails.yAxisDetails!);

        bool inRange = withInXRange || withInYRange;
        if (!inRange && pointIndex + 1 < dataPoints.length) {
          final CartesianChartPoint<dynamic> nextPoint = dataPoints[pointIndex + 1];
          withInXRange = withInRange(nextPoint.xValue, seriesRendererDetails.xAxisDetails!);
          withInYRange = nextPoint.yValue != null && withInRange(nextPoint.yValue, seriesRendererDetails.yAxisDetails!);
          inRange = withInXRange || withInYRange;
          if (!inRange && pointIndex - 1 >= 0) {
            final CartesianChartPoint<dynamic> prevPoint = dataPoints[pointIndex - 1];
            withInXRange = withInRange(prevPoint.xValue, seriesRendererDetails.xAxisDetails!);
            withInYRange =
                prevPoint.yValue != null && withInRange(prevPoint.yValue, seriesRendererDetails.yAxisDetails!);
          }
        }
        if (withInXRange || withInYRange) {
          seriesRendererDetails.calculateRegionData(
              stateProperties, seriesRendererDetails, seriesIndex, currentPoint, pointIndex,);
          if ((currentPoint.isVisible && !currentPoint.isGap) && startPoint == null) {
            startPoint = currentPoint;
          }
          if (pointIndex + 1 < dataPoints.length) {
            nextPoint = dataPoints[pointIndex + 1];
            if (startPoint != null && !nextPoint.isVisible && nextPoint.isGap) {
              startPoint = null;
            } else if (nextPoint.isVisible && !nextPoint.isGap) {
              endPoint = nextPoint;
            }
          }

          if (startPoint != null && endPoint != null) {
            seriesRendererDetails.drawSegment(canvas,
                seriesRenderer._createSegments(startPoint, endPoint, segmentIndex += 1, seriesIndex, animationFactor),);
            endPoint = startPoint = null;
          }
        }
      }
      clipRect = calculatePlotOffset(
          Rect.fromLTRB(
              stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width,
              stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height,
              stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width,
              stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height,),
          Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset),);

      canvas.restore();
      if ((series.animationDuration <= 0 ||
              (!renderingDetails.initialRender! && seriesRendererDetails.needAnimateSeriesElements == false) ||
              animationFactor >= stateProperties.seriesDurationFactor) &&
          (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) {
        canvas.clipRect(clipRect);
        seriesRendererDetails.renderSeriesElements(chart, canvas, seriesRendererDetails.seriesElementAnimation);
      }
      if (animationFactor >= 1) {
        stateProperties.setPainterKey(seriesIndex, painterKey.name, true);
      }
    }
  }

  @override
  bool shouldRepaint(LineChartPainter oldDelegate) => isRepaint;
}
